﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace HF
{
    public static partial class HExpr
    {
        /// <summary>
        /// 通过 <see cref="Expression"/> 构建调用对象方法的匿名函数
        /// </summary>
        public static class Method
        {
            private static readonly ConcurrentDictionary<MethodInfo, Lazy<(Func<object, object[], object>, Delegate)>> _Map = new ();

            /// <summary>
            /// 通过 <see cref="Expression"/> 动态编译(静态或实体)方法的匿名方法
            /// </summary>
            /// <param name="type">对象类型</param>
            /// <param name="methodName">方法名称, 区分大小写</param>
            /// <param name="types">参数类型集合, 空参数可为 <see langword="null"/></param>
            /// <param name="func">
            /// 返回的匿名函数
            /// <list type="bullet">
            /// <item>参数1: 实体对象,为静态函数时可为 <see langword="null"/></item>
            /// <item>参数2: <paramref name="methodName"/> 方法参数集合, 空参数可为 <see langword="null"/></item>
            /// <item>返回值: 返回 <see cref="object"/>, 需自己转换</item>
            /// </list>
            /// </param>
            /// <param name="fast">
            /// 高效匿名函数,可以查看 <see cref="Delegate.Method.Name"/> 获取字符串定义
            /// </param>
            public static void Complice(Type type, string methodName, Type[] types, out Func<object, object[], object> func, out Delegate fast)
            {
                const BindingFlags C_BF = C_DEF_BF | BindingFlags.Instance | BindingFlags.Static;
                type.CheckArgumentNull(nameof(type));
                methodName = methodName.CheckArgumentNull(nameof(methodName)).Trim();
                types ??= Type.EmptyTypes;
                var method = type.GetMethod(methodName, C_BF, null, types, null);
                if (method == null)
                {
                    var ts = types.Select(v => v.GetTypeFullName())
                            .Aggregate((u, d) => $"{u}, {d}");

                    var msg = $"未能从类型{type.GetTypeFullName()} 中获取到名为 '{methodName}({ts})' 的方法";
                    new Exception(msg).Throw();
                }

                if (!_Map.TryGetValue(method, out var lazy))
                {
                    
                    Complice(method, out func, out fast);
                }
                else
                {
                    func = lazy.Value.Item1;
                    fast = lazy.Value.Item2;
                }
            }

            /// <summary>
            /// 通过 <see cref="Expression"/> 动态编译(静态或实体)方法的匿名方法
            /// </summary>
            /// <param name="method">对象方法信息</param>
            /// <param name="types">参数类型集合, 空参数可为 <see langword="null"/></param>
            /// <param name="func">
            /// 返回的匿名函数,参数和返回值都为 <see cref="object"/>,兼容性强
            /// <list type="bullet">
            /// <item>参数1: 实体对象,为静态函数时可为 <see langword="null"/></item>
            /// <item>参数2: <paramref name="methodName"/> 方法参数集合, 空参数可为 <see langword="null"/></item>
            /// <item>返回值:
            /// <para>1:有返回值的返回 <see cref="object"/>, 结果需自己转换</para>
            /// <para>2:返回值为 <see cref="void"/> 的返回 <see langword="null"/> </para>
            /// </item>
            /// </list>
            /// </param>
            /// <param name="fast">
            /// 高效匿名函数,可以查看 <see cref="Delegate.Method.Name"/> 获取字符串定义
            /// <para>性能接近本地直接调用</para>
            /// </param>
            /// <exception cref="ArgumentNullException"> 参数 <paramref name="method"/> 为null 时触发</exception>
            public static void Complice(MethodInfo method, out Func<object, object[], object> func, out Delegate fast)
            {
                method.CheckArgumentNull(nameof(method));
                if (!_Map.TryGetValue(method, out var lazy))
                {
                    lazy = _Map.GetOrAdd(method, v => createLazy(v));
                    _Map.TryAdd(method, lazy);
                }
                func = lazy.Value.Item1;
                fast = lazy.Value.Item2;

                Lazy<(Func<object, object[], object>, Delegate)> createLazy(MethodInfo param)
                {
                    var result = new Lazy<(Func<object, object[], object>, Delegate)>(() =>
                    {
                        Func<object, object[], object> r1;
                        Delegate r2;
                        var paramTypes = param.GetParameters().Select(v => v.ParameterType).ToArray();
                        {
                            var ep1 = Expression.Parameter(typeof(object), "p1");
                            var ep2 = Expression.Parameter(typeof(object[]), "p1");                            
                            var ec1 = param.DeclaringType.IsValueType ?
                                Expression.Unbox(ep1, param.DeclaringType) :
                                Expression.Convert(ep1, param.DeclaringType);

                            var ec2 = paramTypes.Select((v, i) =>
                                ConvertExpr(Expression.ArrayAccess(ep2, Expression.Constant(i)), v))
                                .ToArray();

                            Expression eCall = method.IsStatic ?
                                Expression.Call(null, method, ec2) :
                                Expression.Call(ec1, method, ec2);

                            if (method.ReturnType == typeof(void))
                            {
                                //更改返回值为 void 的方法为 null
                                eCall = Expression.Block(eCall, Expression.Constant(null));
                            }
                            else if (method.ReturnType.IsValueType)
                            {
                                eCall = Expression.Convert(eCall, typeof(object));
                            }

                            var eps = new ParameterExpression[] { ep1, ep2 };
                            var lambda = Expression.Lambda<Func<object, object[], object>>(eCall, eps);
                            r1 = lambda.Compile();
                        }

                        if (method.IsStatic)    //静态函数
                        {
                            var types = paramTypes.Concat(new[] { method.ReturnType }).ToArray();
                            var et = Expression.GetDelegateType(types);
                            r2 = Delegate.CreateDelegate(et, method);
                            return (r1, r2);
                        }

                        //非值类型 或 是引用值类型
                        if (!method.DeclaringType.IsValueType)
                        {
                            var types = new[] { method.DeclaringType }.Concat(paramTypes).Concat(new[] { method.ReturnType }).ToArray();
                            var et = Expression.GetDelegateType(types);
                            r2 = Delegate.CreateDelegate(et, method);
                            return (r1, r2);
                        }

                        {                            
                            var isAction = method.ReturnType == typeof(void);
                            string funName = isAction ? "ActionRef<" : "FuncRef<";
                            var ptypes = new[] { param.DeclaringType }.Concat(paramTypes).ToArray();
                            if (!isAction)
                            {
                                ptypes = ptypes.Concat(new[] { method.ReturnType }).ToArray();
                            }

                            funName += (ptypes.Select(v => v.GetTypeFullName()).Aggregate((u, d) => $"{u}, {d}") + ">");
                            Type et = GetDelegate(ptypes, isAction, true);

                            var ep1 = Expression.Parameter(param.DeclaringType.MakeByRefType(), "p1");
                            var ep2 = paramTypes.Select((v, i) => Expression.Parameter(v, $"p{i + 2}")).ToArray();
                            var pm = new [] { ep1 }.Concat(ep2).ToArray();

                            Expression eCall = Expression.Call(ep1, method, ep2);
                            var lambda = Expression.Lambda(et, eCall, funName, false, pm);
                            r2 = lambda.Compile();
                            return (r1, r2);
                        }
                    }
                    , LazyThreadSafetyMode.ExecutionAndPublication);

                    return result;
                }
            }
        }
    }
}
